package com.ruoyi.handler.websocket;

import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.URLUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;


/**
 * URL参数处理程序，这时候连接还是个http请求，没有升级成webSocket协议，此处SimpleChannelInboundHandler泛型使用FullHttpRequest。
 *
 * @author Nanase Takeshi
 * @date 2022/5/7 15:07
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyWebSocketParamHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    /**
     * 此处进行url参数提取，重定向URL，访问webSocket的url不支持带参数的，带参数会抛异常，这里先提取参数，将参数放入通道中传递下去，重新设置一个不带参数的url。
     *
     * @param ctx     the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
     *                belongs to
     * @param request the message to handle
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {

        //判断当前请求是否是通过移动设备上发出的，如微信小程序等。
//        boolean isMobileDevice = ServletUtils.isMobileDevice(request);
//        log.info("channelRead0 request: {}", request);
        HttpHeaders headers = request.headers();
        String userAgent = headers.get("User-Agent");
        //示例：
        //微信小程序调试工具：
        //  User-Agent: wechatdevtools desktopapp appservice port/59291 token/8c2037b509c03c3e94be581fa5a1ba1f runtime/2
        //Web端Chrome浏览器：
        //  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
        log.info("channelRead0 userAgent: {}", userAgent);


        String remoteAddress = ctx.channel().remoteAddress().toString();
        log.info("NettyWebSocketParamHandler.channelRead0 客户端[{}]请求建立websocket连接, channel: {}", ctx.channel().remoteAddress().toString(), ctx.channel()); //示例：remoteAddress: /0:0:0:0:0:0:0:1:55849
//        log.info("NettyWebSocketParamHandler.channelRead0 request: {}", request); //可以获取到http请求相关详细信息

        //uri示例：
        // ws://123.57.17.21:8082/wj?token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImUwYmEwYTAyLWRiNWItNDgxMi04OWJiLTU3ZTdlNTMyY2IxOCJ9.skD8l4HFcAs95UEL6CT3AO8DH8gVpnD1EjPgw34C5n2v-N8hMubehql8ryfWb901mZjl51Bjs3smrJTSuaDggQ
        String uri = request.uri();
        log.info("NettyWebSocketParamHandler.channelRead0 uri: {}", uri); //示例：uri: /wj?token=123

//        HttpHeaders headers = request.headers();
//        log.info("NettyWebSocketParamHandler.channelRead0 headers: {}", headers);
//        Iterator<Map.Entry<String, String>> iterator = headers.iterator();
//        while (iterator.hasNext()) {
//            Map.Entry<String, String> headerEntry = iterator.next();
//            log.info("NettyWebSocketParamHandler.channelRead0 headerEntry: {}---{}", headerEntry.getKey(), headerEntry.getValue());
//        }

        //先检查当前ws连接请求是否携带token
        Map<CharSequence, CharSequence> queryMap = UrlBuilder.ofHttp(uri).getQuery().getQueryMap();
        AttributeKey<String> tokenAttributeKey = AttributeKey.valueOf("token");
        AttributeKey<String> userAgentAttributeKey = AttributeKey.valueOf("userAgent");
        CharSequence tokenSeq = queryMap.get("token");
        if (Objects.isNull(tokenSeq) || tokenSeq.length() == 0) {
            log.warn("连接未携带token或token为空,服务器主动断开当前ws连接.");
            //如果token校验不通过，发送连接关闭的消息给客户端，设置自定义code和msg用来区分下服务器是因为token不对才导致关闭
            //此时ws连接其实还未建立完成，因为当前还处于http请求阶段，所以此时无法使用ctx发送数据给客户端。
//            ctx.writeAndFlush(new CloseWebSocketFrame(4001, "连接请求非法,服务器主动断开当前ws连接.")).addListener(ChannelFutureListener.CLOSE);
            ctx.close(); //此时调用close方法关闭连接后，客户端接收到的关闭事件closeEvent的状态码为1006，状态信息为""。
            return;
        }

        //将token存入channel AttributeKey中，用于后面的进一步校验
        ctx.channel().attr(tokenAttributeKey).setIfAbsent(tokenSeq.toString());
        ctx.channel().attr(userAgentAttributeKey).setIfAbsent(userAgent);
        request.setUri(URLUtil.getPath(uri));

        ctx.fireChannelRead(request.retain());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        cause.printStackTrace();
        Channel channel = ctx.channel();
        log.error("exceptionCaught execute,channel: {}, cause: {}", channel, cause.getMessage(), cause);
        ctx.close();
    }

}
